package sourceFiles;

import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;

import Exceptions.InvalidAgeParam;
import Exceptions.InvalidCapacity;
import Exceptions.InvalidChildID;
import Exceptions.InvalidHeightParam;
import Exceptions.InvalidRideId;
import Exceptions.TicketDoesntExist;
import Exceptions.CentralComputerExceptions.ChildAgeDoesntMatch;
import Exceptions.CentralComputerExceptions.ChildDoesntHaveTicket;
import Exceptions.CentralComputerExceptions.ChildHeightDoesntMatch;
import Exceptions.CentralComputerExceptions.ChildIDDoesntExist;
import Exceptions.CentralComputerExceptions.RideIDDoesntExist;
import RideExceptions.ChildAlreadyInRideException;
import RideExceptions.ChildNotInRideException;
import RideExceptions.RideActiveException;
import RideExceptions.RideInactiveException;

/**
 * A class representing the Rides in E-Park. When a child wants to enter or exit
 * a ride, this class checks the validity of the entrance or egress to it (some
 * queries to the Central Computer are made in the process). In order to
 * maintain a proper running cycle for a ride, each Ride initiates a
 * java.util.Timer and uses it to schedule activating operations for the ride.
 * Note that java synchronization is included to make the Ride Thread-Safe in
 * methods that can be accessed concurrently (such as activating a ride and
 * querying it's state). This is made to address the issue created when setting
 * a timer that creates a different Java Thread, other than the Main Thread.
 * 
 * @author LASO-Team
 * @version 2.0 (10.8.2013)
 * 
 */

public class Ride implements IRide {

	private Integer rideID;
	private Integer maxCapacity;
	private LinkedList<Integer> childrenIDs;
	private Integer minimumAge;
	private Integer minimumHeight;
//	private Integer timeSinceRideOpened;
	private String rideName;
	private RideState state;
	private ICentralComputer centralComputer;
	private IGate entranceGate;
	private IGate exitGate;
	private Timer timer;
	private int cycleTimeSec;
	private static final int DEFAULT_CYCLE_TIME_SEC = 300;

	/**
	 * A simple runnable that starts the ride that created it when the
	 * scheduling timer finishes counting a delay for the operation
	 */
	private class rideSchedueler extends TimerTask {
		private Ride parentRide;

		public rideSchedueler(Ride parentRide) {
			this.parentRide = parentRide;
		}

		/**
		 * When the Ride timer reaches zero this operation will be called in the
		 * ---timer thread---, resulting in activation of the current parent
		 * Ride
		 */
		@Override
		public void run() {
			parentRide.activateRide();
		}
	}

	/*--------------------------------------------------------------------------*/

	/**
	 * @return true if the current capacity is equal to the maximal capacity,
	 *         false otherwise.
	 */
	private Boolean isAtMaxCapacity() {
		return childrenIDs.size() == maxCapacity;
	}

	/**
	 * This method should only be called by the Timer thread. This method
	 * changes the state of the ride to be RUNNING. MUST be synchronized with
	 * methods that query the Gates and the state of the Ride (RUNNING/STANDBY).
	 * This method should only be called by the Timer thread.
	 */
	synchronized private void activateRide() {
		entranceGate.closeGate();
		state = RideState.RUNNING;
		exitGate.openGate();
		if (childrenIDs.size() == 0) {
			deActivateRide();
		}
	}

	/**
	 * This method should only be called by the Timer thread. This method
	 * changes the state of the ride to be STANDBY. MUST be synchronized with
	 * methods that query the Gates and the state of the Ride (RUNNING/STANDBY)
	 */
	synchronized private void deActivateRide() {
		exitGate.closeGate();
		state = RideState.STANDBY;
		entranceGate.openGate();
		timer.schedule(new rideSchedueler(this), cycleTimeSec * 1000);
	}

	/*--------------------------------------------------------------------------*/

	/**
	 * A constructor for the Ride class. in addition to all the other
	 * parameters, receives an int indicating the number of second that a ride
	 * will admit children in it's entrance gate before gate closing
	 * 
	 * @param rideID
	 *            the ride's ID.
	 * @param maxCapacity
	 *            the maximal capacity of users inside the ride.
	 * @param minimumAge
	 *            the minimal age of a child that is allowed to enter the ride.
	 * @param minimumHeight
	 *            the minimal height of a child that is allowed to enter the
	 *            ride.
	 * @param rideName
	 *            the ride's name.
	 * @param centralComputer
	 *            the computer that the ride is registered to.
	 * @param cycleTimeInSeconeds
	 *            the time (in seconds) that the ride cycle
	 */
	public Ride(Integer rideID, Integer maxCapacity, Integer minimumAge,
			Integer minimumHeight, String rideName,
			ICentralComputer centralComputer, int cycleTimeInSeconeds) {
		super();
		this.rideID = rideID;
		this.maxCapacity = maxCapacity;
		this.childrenIDs = new LinkedList<Integer>();
		this.minimumAge = minimumAge;
		this.minimumHeight = minimumHeight;
		this.rideName = rideName;
		this.state = RideState.STANDBY;
		this.centralComputer = centralComputer;
		this.cycleTimeSec = cycleTimeInSeconeds;
		entranceGate = new EntranceGate(this);
		entranceGate.openGate();
		exitGate = new ExitGate(this);
		// starting from the lines below, the ride will start running its cycle
		// according to the timer and the number of children in the ride (see
		// documentation for RideSchedualer, deActivateRide, activateRide,
		// childEntersRide and childExitsRide, as well as the class description
		timer = new Timer();
		timer.schedule(new rideSchedueler(this), cycleTimeSec * 1000);
	}

	/**
	 * A constructor for the Ride class. Sets the default ride cycle time (the
	 * time indicating how long can children enter the ride) to 300 seconds (5
	 * minutes)
	 * 
	 * @param rideID
	 *            the ride's ID.
	 * @param maxCapacity
	 *            the maximal capacity of users inside the ride.
	 * @param minimumAge
	 *            the minimal age of a child that is allowed to enter the ride.
	 * @param minimumHeight
	 *            the minimal height of a child that is allowed to enter the
	 *            ride.
	 * @param rideName
	 *            the ride's name.
	 * @param centralComputer
	 *            the computer that the ride is registered to.
	 */

	public Ride(Integer rideID, Integer maxCapacity, Integer minimumAge,
			Integer minimumHeight, String rideName,
			ICentralComputer centralComputer) {
		this(rideID, maxCapacity, minimumAge, minimumHeight, rideName,
				centralComputer, DEFAULT_CYCLE_TIME_SEC); 
	}

	/* (non-Javadoc)
	 * @see sourceFiles.IRide#configRideParams(java.lang.Integer, java.lang.Integer, java.lang.Integer)
	 */
	@Override
	public void configRideParams(Integer maxCapacity, Integer minAge,
			Integer minHeight) throws InvalidCapacity, InvalidHeightParam,
			InvalidAgeParam {
		if (maxCapacity < 1) {
			throw new InvalidCapacity();
		}
		if (minAge < 1) {
			throw new InvalidAgeParam();
		}
		if (minHeight < 1) {
			throw new InvalidHeightParam();
		}
		this.maxCapacity = maxCapacity;
		this.minimumAge = minAge;
		this.minimumHeight = minHeight;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.IRide#getMaxCapacity()
	 */
	@Override
	public Integer getMaxCapacity() {
		return maxCapacity;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.IRide#getRideName()
	 */
	@Override
	public String getRideName() {
		return rideName;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.IRide#getMinimumAge()
	 */
	@Override
	public Integer getMinimumAge() {
		return minimumAge;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.IRide#getMinimumHeight()
	 */
	@Override
	public Integer getMinimumHeight() {
		return minimumHeight;
	}




	/* (non-Javadoc)
	 * @see sourceFiles.IRide#getRideID()
	 */
	@Override
	public Integer getRideID() {
		return rideID;
	}

	
	/* (non-Javadoc)
	 * @see sourceFiles.IRide#getState()
	 */
	@Override
	synchronized public RideState getState() {
		return state;
	}

	/* (non-Javadoc)
	 * @see sourceFiles.IRide#childEntersRide(java.lang.Integer)
	 */
	@Override
	public void childEntersRide(Integer childID) throws RideActiveException,
			InvalidChildID, ChildAlreadyInRideException, ChildIDDoesntExist,
			InvalidRideId, ChildHeightDoesntMatch, ChildAgeDoesntMatch,
			ChildDoesntHaveTicket, RideIDDoesntExist, TicketDoesntExist {
		if (!entranceGate.IsGateOpened()) {
			throw new RideActiveException();
		}
		if (childID < 1) {
			throw new InvalidChildID();
		}
		if (childrenIDs.contains(childID)) {
			throw new ChildAlreadyInRideException();
		}
		centralComputer.validateChildForRide(rideID, childID);
		childrenIDs.add(childID);
		// checks if the size of the list childIDs equals to the maximal
		// capacity
		if (isAtMaxCapacity()) {
			activateRide();
		}
	}

	/* (non-Javadoc)
	 * @see sourceFiles.IRide#childExitsRide(java.lang.Integer)
	 */
	@Override
	public void childExitsRide(Integer childID) throws RideInactiveException,
			InvalidChildID, ChildNotInRideException {
		if (!exitGate.IsGateOpened()) {
			throw new RideInactiveException();
		}
		if (childID < 1) {
			throw new InvalidChildID();
		}
		if (!childrenIDs.contains(childID)) {
			throw new ChildNotInRideException();
		}
		childrenIDs.remove(childID);
		if (childrenIDs.size() == 0) {
			deActivateRide();
		}
	}



	/* (non-Javadoc)
	 * @see sourceFiles.IRide#getCentralComputer()
	 */
	@Override
	public ICentralComputer getCentralComputer() {
		return centralComputer;
	}



	/* (non-Javadoc)
	 * @see sourceFiles.IRide#getEntranceGate()
	 */
	@Override
	synchronized public IGate getEntranceGate() {
		return entranceGate;
	}



	/* (non-Javadoc)
	 * @see sourceFiles.IRide#getExitGate()
	 */
	@Override
	synchronized public IGate getExitGate() {
		return exitGate;
	}


}
